import socket
import threading
import time
import queue
import os
from tools import MessageTool as MT

savedir = './clientdir'  # 文件保存的文件夹
updir = './updir'
'''
假设服务器的用户编号为0
'''


class ClientPrj5:
    def __init__(self, serversock):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.msgQueue = queue.Queue()  # 向GUI传递消息的队列
        self.serversock = serversock
        self.prvt_pblc = None  # 标识传输的列表是公共的还是私有的 MT.PUBLIC, MT.PRIVATE
        for dir in [savedir, updir]:
            if not os.path.exists(dir):
                os.mkdir(dir)
        self.fileList = os.listdir(savedir)
        self.s.connect(self.serversock)
        self.offLineFile = None
        self.prvtDic = {}  # 存储编号：文件名, 向服务器下载时用
        self.pblcDic = {}  # 存储编号：文件名, 向服务器下载时用
        self.writerDic = {}  # writer字典  文件编号:本地writer句柄 下载时用
        self.nameReader = {}  # 发送文件名与reader对应关系  文件名：reader句柄
        self.userDic = {}  # 用户字典   用户编号：用户名
        self.sendSize = 1024  # chunk的大小
        self.recvFileDic = {}  # 接收文件的编号（自己给的）：文件编号
        self.filenumQueue = queue.Queue()  # 用于接收文件前为该文件产生文件编号
        for i in range(1, 10):
            self.filenumQueue.put(i)
        '''
        暂时没用上，不知道怎么用，但是应该有用吧？
        self.sendEvent = threading.Event()  # 传输事件
        self.sendEventSetLock = threading.Lock()  # 传输事件锁, 防止死锁用
        '''

    def send(self, function, status=0, userNum=0, filenum=0, message=bytes([0])):
        '''
        发送消息
        :param msg:字节形式的消息内容
        :return:
        '''
        msg_packed = MT.packing(MT.int2bytes(1, function, status, userNum, filenum), message)
        while True:
            try:
                self.s.sendall(msg_packed)
                # print("发送成功", msg_packed)
                return True
            except BaseException as e:
                print(e)
                return False

    def login(self, ID, password):
        self.ID=ID
        # 发送账号
        self.send(MT.C_LOGIN, message=ID.encode('utf-8'))
        # 查看第一轮消息
        head = self.s.recv(2)
        if len(head) == 0:
            print('登录失败')
            return None
        length = int.from_bytes(head, byteorder='big', signed=False)  # 先获得长度
        data = self.s.recv(length)
        ms = MT.unpacking(data)
        if not (ms.function == MT.S_LOGIN and ms.status == MT.SUCCESS):
            return ms.function, ms.status
        ints = [i for i in MT.bytes2int(4, ms.message)]
        chapOut = MT.chap(ints, int(password))
        self.send(MT.C_QUEST_RESULT, message=MT.int2bytes(4, chapOut))
        head = self.s.recv(2)
        if len(head) == 0:
            print('登录失败')
            return None
        length = int.from_bytes(head, byteorder='big', signed=False)  # 先获得长度
        data = self.s.recv(length)
        ms = MT.unpacking(data)
        if not (ms.function == MT.S_QUEST_RESULT and ms.status == MT.SUCCESS):
            print('登录失败')
            return MT.FAILED
        else:
            print('登录成功！')
            return MT.SUCCESS

    def changePswd(self, pswd):
        self.send(MT.C_PASSWORD, message=MT.int2bytes(4, pswd))

    def privateFolder(self):
        self.send(MT.C_LIST, MT.PRIVATE)
        self.prvt_pblc = MT.PRIVATE
        self.prvtDic = {}

    def publicFolder(self):
        self.send(MT.C_LIST, MT.PUBLIC)
        self.prvt_pblc = MT.PUBLIC
        self.pblcDic = {}

    # 下载申请
    def downRequest(self, *numbers):
        '''
        下载请求
        :param numbers:请求的文件的编号
        :return:
        '''
        for num in numbers:
            self.send(MT.C_DOWN, filenum=num)
            time.sleep(0.1)  # 延时是为了测试服务器在有文件正在传输时，能否对下载请求正常处理

    # 上传申请
    def upRequest(self, *paths):
        '''
        上传请求
        :param paths: 上传的文件路径
        :return:
        '''
        for path in paths:
            # 先打开文件，准备好reader防止给出错误路径导致服务器忙等
            try:
                name = path.split('/')[-1]
                self.nameReader[name] = open(path, 'rb')
            except:
                print('文件打开失败')
            else:
                self.send(MT.C_UP, message=name.encode('utf-8'))
            time.sleep(0.1)

    # 以上是旧有的功能，以下是新功能
    def sendText(self, usernum, msg):
        '''
        用户消息
        :param usernum: 目标用户编号
        :param msg: 待发送信息
        :return:
        '''
        self.send(MT.T_USER_MESSAGE, userNum=usernum, message=msg.encode('utf-8'))
        self.msgQueue.put((1, '%s %s\n %s'
                                   % ('me', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
                                      msg)))

    def sendFileRequest(self, usernum, *paths):
        '''
        发送文件请求
        :param usernum:目标用户编号
        :param filepath: 待发送文件路径
        :return:
        '''
        for path in paths:
            # 先打开文件，准备好reader防止给出错误路径导致服务器忙等
            try:
                name = path.split('/')[-1]
                self.nameReader[name] = open(path, 'rb')
            except:
                print('文件打开失败')
            else:
                self.send(MT.T_USER_FILE_REQ, userNum=usernum, message=name.encode('utf-8'))
            time.sleep(0.1)

    def offLineTransRequest(self, username, *paths):  # 离线传输，一次仅能传输一个文件
        '''
        离线传输请求函数
        :param username:用户名
        :param paths:文件地址
        :return:
        '''
        for path in paths:
            # 先打开文件，准备好reader防止给出错误路径导致服务器忙等
            try:
                name = path.split('/')[-1]
                self.offLineFile = open(path, 'rb')
            except:
                print('文件打开失败')
            else:
                msg = '%s %s' % (username, name)
                self.send(MT.C_OFFLINE, message=msg.encode('utf-8'))
            time.sleep(0.1)

    def sendFileResponse(self, status, usernum, filenum, filename):
        '''
        文件传输请求响应
        :param status: 状态0或1
        :param username:目标请求方
        :param filenum: 给文件分配的编号
        :param filepath: 对方文件路径
        :return:
        '''
        if status == MT.ALLOW:
            r = open(MT.locJoin(savedir, filename.decode('utf-8')), 'wb')  # 打开文件准备写入
            self.recvFileDic[filenum] = r  # 文件句柄写入字典方便接收时取用
        self.send(MT.T_USER_FILE_RES, status=status, userNum=usernum,
                  filenum=filenum, message=filename)

    def upFile(self, filenum, filename):
        '''
        上传文件
        :param filenum:
        :return:
        '''
        r = self.nameReader[filename]
        chunk = r.read(self.sendSize)
        while chunk:
            self.send(MT.C_UP_TRANS, status=MT.MORE_TEAM, filenum=filenum, message=chunk)
            chunk = r.read(self.sendSize)
        self.send(MT.C_UP_TRANS, status=MT.END, filenum=filenum, message=b'')
        self.nameReader.pop(filename).close()
        print('上传线程关闭')
        self.msgQueue.put((1, '文件%s上传完毕' % filename))

    def sendFile(self, usernum, filenum, filename):
        '''
        向用户传输文件
        :param filenum:
        :return:
        '''
        r = self.nameReader[filename]
        chunk = r.read(self.sendSize)
        while chunk:
            self.send(MT.T_USER_FILE_TRANS, status=MT.MORE_TEAM, userNum=usernum, filenum=filenum, message=chunk)
            chunk = r.read(self.sendSize)
        self.send(MT.T_USER_FILE_TRANS, status=MT.END, userNum=usernum, filenum=filenum, message=b'')
        self.nameReader.pop(filename).close()
        print('发送线程关闭')
        self.msgQueue.put((1, '文件%s传送完毕' % filename))

    def sendOffline(self, filenum):  # 离线传输，一次仅能传输一个文件
        r = self.offLineFile
        chunk = r.read(self.sendSize)
        while chunk:
            self.send(MT.C_OFFLINE_TRANS, status=MT.MORE_TEAM, filenum=filenum, message=chunk)
            chunk = r.read(self.sendSize)
        self.send(MT.C_OFFLINE_TRANS, status=MT.END, filenum=filenum, message=b'')
        r.close()
        print('离线发送线程关闭')
        self.msgQueue.put((1, '离线文件传输完毕'))

    def getMessage(self):
        '''
        接收并解装消息
        :return: 返回解封完毕的消息
        '''
        head = self.s.recv(2)
        if len(head) == 0:
            return None
        length = int.from_bytes(head, byteorder='big', signed=False)  # 先获得长度
        data = self.s.recv(length)
        if len(data) == 0:
            return None
        ms = MT.unpacking(data)
        return ms

    def autoDown(self):
        self.privateFolder()
        time.sleep(0.5)  # 等文件列表发过来了再开始下载
        '''
        length = len(self.prvtDic)
        self.changeFolder = threading.Semaphore(length)  # 在传输文件的同时，是不能够进行列表申请的，因此，需要一个信号量来查看何时文件传输结束
        for i in range(length):
            self.changeFolder.acquire()
        for k, v in self.prvtDic.items():
            if v not in self.fileList:
                self.downRequest(k)
        for i in range(length):
            self.changeFolder.acquire()
        '''
        self.publicFolder()
        time.sleep(0.5)  # 等文件列表发过来了再开始下载
        '''
        for k, v in self.pblcDic.items():
            if v not in self.fileList:
                self.downRequest(k)
        '''

    def listen(self):
        while True:
            ms = self.getMessage()
            if not ms:
                self.close()
                return
            if ms.function == MT.S_PASSWORD:
                if ms.status == MT.SUCCESS:
                    print('密码修改成功')
                    self.msgQueue.put((1, '密码修改成功'))
                elif ms.status == MT.FAILED:
                    print(ms.message.decode('utf-8'))
                    self.msgQueue.put((1, ms.message.decode('utf-8')))
            elif ms.function == MT.S_LIST:
                if ms.status == MT.SUCCESS:
                    print('文件列表请求成功')
                    self.msgQueue.put((1, '文件列表请求成功'))
                elif ms.status == MT.FAILED:
                    print(ms.message.decode('utf-8'))
                    self.msgQueue.put((1, '文件列表请求失败,%s' % ms.message.decode('utf-8')))
            elif ms.function == MT.S_LIST_TRANS:
                if self.prvt_pblc == MT.PUBLIC:
                    if ms.status == MT.MORE_TEAM:
                        print(ms.fileNumber, ms.message.decode('utf-8'))
                        self.pblcDic[ms.fileNumber] = ms.message.decode('utf-8')
                        self.msgQueue.put((3, self.pblcDic))
                    elif ms.status == MT.END:
                        print(ms.fileNumber, ms.message.decode('utf-8'))
                        print('文件列表传输结束')
                        self.msgQueue.put((1, '公共文件列表传输结束'))
                elif self.prvt_pblc == MT.PRIVATE:
                    if ms.status == MT.MORE_TEAM:
                        print(ms.fileNumber, ms.message.decode('utf-8'))
                        self.prvtDic[ms.fileNumber] = ms.message.decode('utf-8')
                        self.msgQueue.put((2, self.prvtDic))
                    elif ms.status == MT.END:
                        print(ms.fileNumber, ms.message.decode('utf-8'))
                        print('文件列表传输结束')

                        self.msgQueue.put((1, '私密文件列表传输结束'))
            elif ms.function == MT.S_DOWN:
                if ms.status == MT.ALLOW:
                    if self.prvt_pblc == MT.PUBLIC:
                        self.writerDic[ms.fileNumber] = open(MT.locJoin(savedir, self.pblcDic[ms.fileNumber]), 'wb')
                        print('文件%s开始下载' % self.pblcDic[ms.fileNumber])
                        self.msgQueue.put((1, '文件%s开始下载' % self.pblcDic[ms.fileNumber]))
                    elif self.prvt_pblc == MT.PRIVATE:
                        self.writerDic[ms.fileNumber] = open(MT.locJoin(savedir, self.prvtDic[ms.fileNumber]), 'wb')
                        print('文件%s开始下载' % self.prvtDic[ms.fileNumber])
                        self.msgQueue.put((1, '文件%s开始下载' % self.prvtDic[ms.fileNumber]))
                elif ms.status == MT.REFUSE:
                    print('服务器拒绝了文件%s的下载请求, 原因是%s' % (ms.fileNumber, ms.message.decode('utf-8')))
                    self.msgQueue.put((1, '服务器拒绝了文件%s的下载请求, 原因是%s'
                                       % (ms.fileNumber, ms.message.decode('utf-8'))))
            elif ms.function == MT.S_DOWN_TRANS:
                if ms.status == MT.MORE_TEAM:
                    self.writerDic[ms.fileNumber].write(ms.message)
                elif ms.status == MT.END:
                    self.writerDic[ms.fileNumber].write(ms.message)
                    self.writerDic.pop(ms.fileNumber).close()
                    print('传送完毕')
                    self.msgQueue.put((1, '%d号文件下载完毕' % ms.fileNumber))
            elif ms.function == MT.S_UP:
                name = ms.message.decode('utf-8')
                if ms.status == MT.ALLOW:
                    threading.Thread(target=self.upFile, args=(ms.fileNumber, name,)).start()
                    # sendEventSetLock.acquire()
                    # sendEvent.set()
                    # sendEventSetLock.release()
                elif ms.status == MT.REFUSE:
                    print('服务器拒绝了文件%s上传' % name)
                    self.nameReader.pop(name).close()  # 从字典中清除句柄并关闭文件
                    self.msgQueue.put((1, '服务器拒绝了文件%s上传' % name))
            elif ms.function == MT.S_USER_LIST:
                if ms.status == MT.APPEND:
                    self.userDic[ms.userNumber] = ms.message.decode('utf-8')
                    print(ms.userNumber, ms.message.decode('utf-8'))
                    self.msgQueue.put((4, (self.userDic)))
                elif ms.status == MT.DELETE:
                    self.userDic.pop(ms.userNumber)
                    print('用户%d %s下线了' % (ms.userNumber, ms.message.decode('utf-8')))
                    self.msgQueue.put((4, (self.userDic)))
            elif ms.function == MT.T_USER_MESSAGE:
                print(self.userDic[ms.userNumber], ms.message.decode('utf-8'))
                if self.ID!=self.userDic[ms.userNumber]:
                    self.msgQueue.put((1, '%s %s\n %s'
                                       % (self.userDic[ms.userNumber], time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
                                          ms.message.decode('utf-8'))))
            elif ms.function == MT.T_USER_FILE_REQ:
                print('来自用户', ms.userNumber, '的文件', ms.message)
                self.msgQueue.put((1, '来自用户%s的文件%s'
                                   % (self.userDic[ms.userNumber], ms.message.decode('utf-8'))))
                filenum = self.filenumQueue.get()
                # 测试时先全部按照同意，与GUI结合时再考虑判断 messagebox
                self.sendFileResponse(MT.ALLOW, ms.userNumber, filenum, ms.message)
            elif ms.function == MT.T_USER_FILE_RES:
                if ms.status == MT.ALLOW:
                    threading.Thread(target=self.sendFile,
                                     args=(ms.userNumber, ms.fileNumber, ms.message.decode('utf-8'),)).start()
                    self.msgQueue.put((1, '用户%s接收了文件%s' %
                                       (self.userDic[ms.userNumber], ms.message.decode('utf-8'))))
                else:
                    print(ms.userNumber, '拒绝了文件', ms.fileNumber, ms.message)
                    self.msgQueue.put((1, '用户%s拒绝了文件%s' %
                                       (self.userDic[ms.userNumber], ms.message.decode('utf-8'))))
            elif ms.function == MT.T_USER_FILE_TRANS:
                if ms.status == MT.MORE_TEAM:
                    self.recvFileDic[ms.fileNumber].write(ms.message)
                elif ms.status == MT.END:
                    self.recvFileDic[ms.fileNumber].write(ms.message)
                    self.msgQueue.put((1, '文件%s接收完毕' %
                                       (self.recvFileDic[ms.fileNumber])))
                    self.recvFileDic.pop(ms.fileNumber).close()
                    self.filenumQueue.put(ms.fileNumber)
                    print('传送完毕')
            elif ms.function == MT.S_OFFLINE:
                if ms.status == MT.ALLOW:
                    self.sendOffline(ms.fileNumber)
                    self.msgQueue.put((1, '允许传输离线文件'))
                else:
                    print('不允许离线文件传输，原因为', ms.message.decode('utf-8'))
                    self.msgQueue.put((1, '允许传输离线文件，原因为%s' % ms.message.decode('utf-8')))

    def run(self):
        threading.Thread(target=self.listen).start()


if __name__ == '__main__':
    c = ClientPrj5(('127.0.0.1', 12345))
    c.login('hys', '12345')  # 开机登录
    c.run()
    c.autoDown()
    #c.sendText(1, 'hello')
    c.sendFileRequest(21, MT.locJoin(updir, 'prvt传文件演示_new.mp4'))
    c.offLineTransRequest('ylh', MT.locJoin(updir, 'ty1111.jpg'))
    c.sendText(21, 'vyu')
